﻿/// <reference path="framework.js" />
RexToy.registerNamespace('RexToy.UI');
RexToy.UI.Capability = {};

RexToy.UI.Capability.CssTransitions = false;
(function () {
    var div = document.createElement('div');
    div.innerHTML = '<div style="-webkit-transition:color 1s linear;-moz-transition:color 1s linear;"></div>';
    //RexToy.UI.Capability.CssTransitions = (div.firstChild.style.webkitTransition !== undefined) || (div.firstChild.style.MozTransition !== undefined);
    RexToy.UI.Capability.CssTransitions = (div.firstChild.style.webkitTransition !== undefined);
    delete div;
})();

RexToy.registerType('RexToy.UI.Point');
RexToy.UI.Point.extend({
    properties: {
        x: 0,
        y: 0
    },

    $constructor: function (x, y) {
        this.__x = x;
        this.__y = y;
    },

    moveBy: function (dx, dy) {
        return new RexToy.UI.Point(this.__x + dx, this.__y + dy);
    }
});

RexToy.registerType('RexToy.UI.Size');
RexToy.UI.Size.extend({
    properties: {
        width: 0,
        height: 0
    },

    $constructor: function (w, h) {
        this.__width = w;
        this.__height = h;
    },

    equal: function (size) {
        if (!size)
            return false;
        return this.__width == size.getWidth() && this.__height == size.getHeight();
    }
});

RexToy.UI.LayoutException = Error;

RexToy.registerNamespace('RexToy.UI.Controls');

RexToy.UI.Controls.Orientation = {
    Horizental: 1,
    Vertical: 0
};

RexToy.UI.Controls.TransitionMode = {
    Slide: 'slide',
    SlideDown: 'slidedown',
    Pop: 'pop'
};

RexToy.UI.Controls.Palette = {
    A: 'a',
    B: 'b',
    C: 'c',
    D: 'd',
    E: 'e'
};

RexToy.UI.Controls.Tag = {
    Div: "div",
    A: "a",
    Span: "span",
    Label: "label",
    Input: "input",
    Img: "img",
    H1: "h1",
    H2: "h2",
    H3: "h3",
    H4: "h4",
    H5: "h5",
    P: "p",
    Select: "select",
    TextArea: "textarea",
    Table: "table",
    Ul: "ul",
    Ol: "ol",
    Li: "li",
    Option: "option"
};

RexToy.registerType('RexToy.UI.Controls.DomNode');
RexToy.UI.Controls.DomNode.extend({
    _element: null,
    _hash: null,

    properties: {
        parent: null
    },

    $constructor: function (domElement) {
        this._element = domElement;
        this.children = [];
    },

    appendChild: function (newDomNode) {
        newDomNode.setParent(this);
        this.children.push(newDomNode);

        this._element.appendChild(newDomNode._element);
    },

    removeChild: function (childNode) {
        childNode.setParent(null);
        this.children.remove(childNode);

        this._element.removeChild(childNode._element);
    },

    insertChildAt: function (childNode, idx) {
        if (idx > this.children.length || idx < 0) {
            throw new RexToy.ArgumentOutOfRangeException('not valid index');
        }
        if (idx == this.children.length) { //Note: this is same as append
            this.appendChild(childNode);
        }
        if (idx < this.children.length) {
            var node = this.children[idx];
            this._element.insertBefore(childNode._element, node._element);
            childNode.setParent(this);
            this.children.insertAt(childNode, idx);
        }
    },

    getChildren: function () {
        return this.children;
    },

    clearChildren: function () {
        while (this._element.hasChildNodes()) {
            this._element.removeChild(this._element.firstChild);
        }
        this.children = [];
    },

    setAttribute: function (nm, val) {
        this._element[nm] = val;
    },

    getAttribute: function (nm) {
        return this._element[nm];
    },

    getStyle: function () {
        return this._element['style'];
    },

    attachEvent: function (evType, fn) {
        if (this._element.attachEvent) {
            this._element.attachEvent(evType, fn);
            return this;
        }
        if (this._element.addEventListener) {
            var name = evType.substr(2); //Note: remove the 'on' prefix
            this._element.addEventListener(name, fn, false);
            return this;
        }
        this._element[evType] = fn;
        return this;
    },

    detachEvent: function (evType, fn) {
        if (this._element.detachEvent) {
            this._element.detachEvent(evType, fn);
            return this;
        }
        if (this._element.removeEventListener) {
            var name = evType.substr(2); //Note: remove the 'on' prefix
            this._element.removeEventListener(name, fn, false);
            return this;
        }
        this._element[evType] = null;
        return this;
    },

    appendClass: function (name) {
        this.replaceClass(String.Empty, name);
    },

    removeClass: function (name) {
        this.replaceClass(name, String.Empty);
    },

    replaceClass: function (oldName, newName) {
        var toRemove = oldName.split(String.Space);
        var toAdd = newName.split(String.Space);
        var str = this._element.className;
        var arr = str.split(String.Space);

        if (!String.isNullOrEmpty(oldName)) {
            for (var i = 0; i < toRemove.length; i++) {
                arr.remove(toRemove[i]);
            }
        }

        if (!String.isNullOrEmpty(newName)) {
            for (var i = 0; i < toAdd.length; i++) {
                var cs = toAdd[i];
                if (arr.contains(cs))
                    continue;
                else
                    arr.push(cs);
            }
        }

        this._element.className = arr.join(String.Space);
    },

    $: function (name) {
        return this._hash[name];
    },

    getAbsoluteX: function () {
        var obj = this._element;
        var curleft = 0;
        if (obj.offsetParent)
            while (1) {
                curleft += obj.offsetLeft;
                if (!obj.offsetParent)
                    break;
                obj = obj.offsetParent;
            }
        else if (obj.x)
            curleft += obj.x;
        return curleft;
    },

    getAbsoluteY: function () {
        var obj = this._element;
        var curtop = 0;
        if (obj.offsetParent)
            while (1) {
                curtop += obj.offsetTop;
                if (!obj.offsetParent)
                    break;
                obj = obj.offsetParent;
            }
        else if (obj.y)
            curtop += obj.y;
        return curtop;
    },

    getWidth: function () {
        return this._element.offsetWidth;
    },

    setWidth: function (val) {
        this._element['style'].width = val + 'px';
    },

    getHeight: function () {
        return this._element.offsetHeight;
    },

    setHeight: function (val) {
        this._element['style'].height = val + 'px';
    }
});

RexToy.UI.Controls.DomNode.create = function (param) {
    var _create = function(json, hash) {
        var element = document.createElement(json['tag']);
        var domNode = new RexToy.UI.Controls.DomNode(element);
        for (var p in json) {
            if (p === 'tag') continue;
            if (p === '$name') {
                hash[json[p]] = domNode;
            }
            if (p === 'items'/*&& param[p] is array*/) {
                for (var i = 0; i < json[p].length; i++) {
                    var subParam = json[p][i];
                    var subNode = _create(subParam, hash);
                    domNode.appendChild(subNode);
                }
            }

            if (typeof json[p] == 'function') continue;
            element[p] = json[p];
        }

        return domNode;
    };
    var hash = {};
    var dom = _create(param, hash);
    dom._hash = hash;
    return dom;
};

RexToy.registerInterface('RexToy.UI.Controls.IControl');
RexToy.UI.Controls.IControl.declare({
    init: RexToy.AbstractFunction,
    destroy: RexToy.AbstractFunction
});

RexToy.registerType('RexToy.UI.Controls.Control', RexToy.Object, [RexToy.UI.Controls.IControl]);
RexToy.UI.Controls.Control.extend({
    properties: {
        domNode: null,
        standalone: true,
        orientation: RexToy.UI.Controls.Orientation.Vertical,
        palette: String.Empty,
        parent: null,
        selectable: false,
        dragEnable: false, //Note: if the control support drag. (if support, startDrag/stopDrag/dragging will be called.)
        dragEventBubble: true, //Note: propagate the drag event. (if propagate, it will notify the parent.)
        isContainer: false
    },

    _initialized: false,

    $constructor: function (param) {
        //TODO:Bug:Need consider base class' events[]!!
        if (this.events && this.events instanceof Array) {
            this.__eventHandlers = {};
            for (var idx = 0; idx < this.events.length; idx++) {
                this.__eventHandlers[this.events[idx]] = [];
            }
        }
    },

    //Note: interface implement
    init: function () {
        if(!this._initialized){
            if (this.__domNode) {
                this.__domNode.attachEvent('onmousedown', this._mousedown.bind(this));

                if (!this.__selectable) {
                    this.__domNode.appendClass('noselect');
                    this.__domNode.setAttribute('unselectable', 'on');
                }
            }
        }
        this._initialized = true;
    },

    _mousedown: function (e) {
        RexToy.UI.Drag.setDragging(this);
        RexToy.UI.Drag.setMouseOffset(RexToy.UI.Mouse.getCoords(e));
        if (e.stopPropagation)
            e.stopPropagation();
        else
            e.cancelBubble = true; //Note: for IE
        //if(e.preventDefault)
        //    e.preventDefault();
    },

    attachEvent: function (nm, handler) {
        var handlers = this.__eventHandlers[nm];
        if (!handlers.contains(handler)) {
            handlers.push(handler);
        }
    },

    detachEvent: function (nm, handler) {
        this.__eventHandlers[nm].remove(handler);
    },

    __fireEvent: function (nm) {
        var handlers = this.__eventHandlers[nm];
        for (var idx = 0; idx < handlers.length; idx++) {
            handlers[idx](this);
        }
    },

    renderInto: function (element) {
        if (!this._initialized)
            this.init();

        if (!element)
            element = RexToy.UI.Window.getDocumentBody();

        if (element instanceof RexToy.UI.Controls.DomNode)
            element.appendChild(this.__domNode);
        else {
            var domNode = new RexToy.UI.Controls.DomNode(element);
            domNode.appendChild(this.__domNode);
        }
    },

    render: function (size) {//Note: virtual method to be called when parent is resize/adjust layout.
    },

    destroy: function () {
        var p = this.__domNode.getParent();
        if (p)
            p.removeChild(this.__domNode);
    },

    setFirst: function (first) {//Note:subclass should override it!
    },

    setLast: function (last) {//Note:subclass should override it!
    },

    setStandalone: function (standalone) {//Note:subclass should override it!
    },

    display: function (show) {
        if (show === false) {
            this.__display = this.__domNode.getStyle().display;
            this.__domNode.getStyle().display = 'none';
        }
        if (show === true) {
            if (this.__display)
                this.__domNode.getStyle().display = this.__display;
            else
                this.__domNode.getStyle().display = null;
        }
    },

    getAbsolutePosition: function () {
        return new RexToy.UI.Point(this.__domNode.getAbsoluteX(), this.__domNode.getAbsoluteY());
    },

    getSize: function () {
        return new RexToy.UI.Size(this.__domNode.getWidth(), this.__domNode.getHeight());
    },

    setWidth: function (val) {
        this.__domNode.setWidth(val);
    },

    setHeight: function (val) {
        this.__domNode.setHeight(val);
    }
});

RexToy.registerType('RexToy.UI.Controls.Container', RexToy.UI.Controls.Control);
RexToy.UI.Controls.Container.extend({
    properties: {
        controls: [],
        isContainer: true,
        sizeUpdate: false //Note: if true, parent will resize the control to fill it.
    },

    appendControl: function (ctrl) { //Note: default implement. derive class can call this or override it.
        ctrl.init();
        ctrl.setParent(this);
        var dom = this.__domNode.$('container');
        dom.appendChild(ctrl.getDomNode());
        this.__controls.push(ctrl);
    },

    removeControl: function (ctrl) { //Note: default implement. derive class can call this or override it.
        ctrl.setParent(null);
        var dom = this.__domNode.$('container');
        dom.removeChild(ctrl.getDomNode());
        this.__controls.remove(ctrl);
    },

    insertControlAt: function (ctrl, idx) { //Note: default implement. derive class can call this or override it.
        ctrl.setParent(this);
        var dom = this.__domNode.$('container');
        dom.insertChildAt(ctrl.getDomNode(), idx);
        this.__controls.insertAt(ctrl, idx);
    },

    clearControls: function () { //Note: default implement. derive class can call this or override it.
        var dom = this.__domNode.$('container');
        for (var i = 0; i < this.__controls.length; i++) {
            dom.removeChild(this.__controls[i].getDomNode());
            this.__controls[i].setParent(null);
        }
        this.__controls = [];
    },
    
    render: function (size) {//Note:base feature, notify all children that size is changed, can be call in subclass when override this.
        var dom = this.__domNode.$('container');
        if (dom) {
            if (this.__sizeUpdate)
                dom.setHeight(size.getHeight());
            var h = dom.getHeight();
            var resizeCtrl = null;            
            for (var i = 0; i < this.__controls.length; i++) {
                var ctrl = this.__controls[i];
                if (ctrl.getIsContainer() && ctrl.getSizeUpdate()) {
                    if (resizeCtrl != null) {
                        throw new RexToy.UI.LayoutException('container can have only 1 resizable control!');
                    } else {
                        resizeCtrl = ctrl;
                    }
                } else {
                    if(ctrl.getIsContainer())
                        ctrl.render(ctrl.getSize());
                    h -= ctrl.getSize().getHeight();
                }
            }
            if (resizeCtrl) {
                resizeCtrl.getDomNode().setHeight(h);
                resizeCtrl.render(new RexToy.UI.Size(dom.getWidth(), h));
            }
        }
    }
});

RexToy.registerType('RexToy.UI.Controls._Window', RexToy.UI.Controls.Control);
RexToy.UI.Controls._Window.extend({
    events: ['onready', 'onresize'],

    $constructor: function () {
        var doc = new RexToy.UI.Controls.DomNode(document);
        doc.attachEvent("onreadystatechange", this.ready.bind(this));
        window.onresize = this.resize.bind(this);
    },

    ready: function () {
        if (document.readyState === 'complete') {
            this.__fireEvent('onready');
        }
    },

    _size: null,
    resize: function () {
        var newSize = this.getSize();
        if (newSize.equal(this._size))
            return;
        this._size = newSize;
        this.__fireEvent('onresize');
    },

    getDocumentBody: function () {
        var body = document.body;
        return new RexToy.UI.Controls.DomNode(body);
    },

    getSize: function () {
        return new RexToy.UI.Size(window.innerWidth, window.innerHeight);
    },

    getScrollSize: function () {
        var h = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
        var w = Math.max(document.body.scrollWidth, document.documentElement.scrollWidth);
        return new RexToy.UI.Size(w, h);
    }
});

RexToy.UI.$ = function (selector) {
    var element = document.querySelector(selector);
    return new RexToy.UI.Controls.DomNode(element);
};

RexToy.UI.$$ = function (selector) {
    var ret = [];
    var arr = document.querySelectors(selector);
    for (var i = 0; i < arr.length; i++) {
        ret.push(new RexToy.UI.Controls.DomNode(arr[i]));
    }
    return arr;
};

RexToy.UI.Window = new RexToy.UI.Controls._Window();
RexToy.UI.Document = new RexToy.UI.Controls.DomNode(window.document);

RexToy.UI.Document.getScrollPosition = function () {
    var scrollX = 0, scrollY = 0;
    if (document.body && typeof document.body.scrollTop != "undefined") {
        scrollX += document.body.scrollLeft;
        scrollY += document.body.scrollTop;
        if (document.body.parentNode && typeof document.body.parentNode.scrollTop != "undefined") {
            scrollX += document.body.parentNode.scrollLeft;
            scrollY += document.body.parentNode.scrollTop;
        }
    } 
    else if (typeof window.pageXOffset != "undefined") 
    {
        scrollX += window.pageXOffset;
        scrollY += window.pageYOffset;
    }
    return new RexToy.UI.Point(scrollX, scrollY);
};

RexToy.UI.Mouse = {
    getCoords: function (e) {
        e = e || window.event;
        if (e.pageX || e.pageY) {
            return new RexToy.UI.Point(e.pageX, e.pageY);
        } else {
            var scroll = RexToy.UI.Document.getScrollPosition();
            return new RexToy.UI.Point(e.clientX + scroll.getX(), e.clientY + scroll.getY());
        }
    }
};

RexToy.registerType('RexToy.UI._Drag');
RexToy.UI._Drag.extend({
    properties: {
        dragging: null,
        mouseOffset: null
    },

    _mouseEventAttached: false,

    $constructor: function () {
        if (!this._mouseEventAttached) {
            RexToy.UI.Document.attachEvent('onmousemove', this.mouseMove.bind(this));
            RexToy.UI.Document.attachEvent('onmouseup', this.mouseUp.bind(this));
            //RexToy.UI.Document.attachEvent('onmouseout', this.mouseOut.bind(this));
            this._mouseEventAttached = true;
        }
    },

    setDragging: function (val) {
        var d = this.getDraggingControl(val);
        this.__dragging = d;
        if (d) {
            if (d.startDrag) {
                d.startDrag();
            }
        }
    },

    getDraggingControl: function (ctrl) {//Note:Detect which control to drag
        if (ctrl.getDragEnable())
            return ctrl;
        else {
            if (ctrl.getDragEventBubble()) {
                var parent = ctrl.getParent();
                if (!parent)
                    return null;
                else
                    return this.getDraggingControl(parent);
            } else {
                return null;
            }
        }
    },

    mouseMove: function (e) {
        if (this.__dragging) {
            var mousePos = RexToy.UI.Mouse.getCoords(e);
            var delta = { x: mousePos.getX() - this.__mouseOffset.getX(), y: mousePos.getY() - this.__mouseOffset.getY() };

            if (this.__dragging.dragging) {
                this.__dragging.dragging(delta);
            }
        }
    },

    _stop: function (e) {
        if (this.__dragging && this.__dragging.stopDrag) {
            var mousePos = RexToy.UI.Mouse.getCoords(e);
            var delta = { x: mousePos.getX() - this.__mouseOffset.getX(), y: mousePos.getY() - this.__mouseOffset.getY() };
            this.__dragging.stopDrag(delta);
        }
        this.__dragging = null;
    },

    mouseUp: function (e) {
        this._stop(e);
    },

    mouseOut: function (e) {
        this._stop(e);
    }
});

RexToy.UI.Drag = new RexToy.UI._Drag();